import SourceKittenFramework
import SwiftLintFramework
import TestHelpers
import XCTest

final class ConfigPathResolutionTests: SwiftLintTestCase {
    private func fixturePath(_ scenario: String) -> URL {
        URL(fileURLWithPath: #filePath)
            .deletingLastPathComponent()
            .appending(path: "Resources")
            .appending(component: scenario)
    }

    /// Returns the paths of lintable files relative to the fixture directory.
    private func lintableFilePaths(in scenario: String, configFile: String? = nil, inPath: String = ".") -> [String] {
        let scenarioPath = fixturePath(scenario).filepath

        let previousDir = FileManager.default.currentDirectoryPath
        defer {
            _ = FileManager.default.changeCurrentDirectoryPath(previousDir)
        }
        XCTAssert(FileManager.default.changeCurrentDirectoryPath(scenarioPath))

        let config = Configuration(configurationFiles: configFile.map { [$0] } ?? [])
        let files = config.lintableFiles(
            inPath: inPath,
            forceExclude: false,
            excludeByPrefix: false
        )

        return files.map { $0.path!.path(relativeTo: scenarioPath) }.sorted()
    }

    func testParentChildSameDirectory() {
        XCTAssertEqual(
            lintableFilePaths(in: "_1_parent_child_same_dir", configFile: "parent.yml"),
            ["Sources/CoreFile.swift"]
        )
    }

    func testParentChildDifferentDirectories() {
        XCTAssertEqual(
            lintableFilePaths(
                in: "_2_parent_child_different_dirs",
                configFile: "project/.swiftlint.yml",
                inPath: "project"
            ),
            ["project/Sources/Core/Service.swift"]
        )
    }

    func testChildOverridesParentExclusion() {
        XCTAssertEqual(
            lintableFilePaths(
                in: "_3_child_overrides_parent_exclusion",
                configFile: "project/.swiftlint.yml",
                inPath: "project"
            ),
            ["project/Vendor/Critical/Important.swift"]
        )
    }

    func testParentIncludesChildExcludes() {
        XCTAssertEqual(
            lintableFilePaths(in: "_1_parent_child_same_dir", configFile: "parent.yml"),
            ["Sources/CoreFile.swift"]
        )
    }

    func testNestedConfigurationBasic() {
        XCTAssertEqual(
            lintableFilePaths(in: "_4_nested_basic", configFile: ".swiftlint.yml"),
            ["ModuleA/File.swift", "ModuleA/Generated/File.swift", "ModuleB/File.swift"]
        )
    }

    func testWildcardPatternCount() {
        XCTAssertEqual(
            lintableFilePaths(
                in: "_5_wildcard_patterns",
                configFile: "project/.swiftlint.yml",
                inPath: "project"
            ),
            ["project/Sources/Models/User.swift"]
        )
    }

    func testLintChildFolder() {
        XCTAssertEqual(
            lintableFilePaths(
                in: "_2_parent_child_different_dirs",
                configFile: "project/.swiftlint.yml",
                inPath: "project"
            ),
            ["project/Sources/Core/Service.swift"]
        )
    }

    func testEmptyIncludedDefaultsToAll() {
        XCTAssertEqual(
            lintableFilePaths(
                in: "_6_wildcards_from_nested_folder",
                configFile: ".swiftlint-exclude-thirdparty.yml"
            ),
            [
                "Generated/API.swift",
                "MyProject/Package.swift",
                "MyProject/Sources/App.swift",
                "MyProject/SubModule/Package.swift",
            ]
        )
    }

    func testMultipleLevelsOfExclusion() {
        XCTAssertEqual(
            lintableFilePaths(in: "_1_parent_child_same_dir", configFile: "parent.yml"),
            ["Sources/CoreFile.swift"]
        )
    }

    func testConfigFromParentFolder() {
        XCTAssertEqual(
            lintableFilePaths(in: "_6_wildcards_from_nested_folder", configFile: ".swiftlint.yml"),
            ["MyProject/Sources/App.swift"]
        )

        XCTAssertEqual(
            lintableFilePaths(in: "_6_wildcards_from_nested_folder/MyProject", configFile: "../.swiftlint.yml"),
            ["Sources/App.swift"]
        )
    }

    func testNestedConfigurationAppliesOnlyToSubdirectory() {
        let scenarioPath = fixturePath("_4_nested_basic")
        let config = Configuration(configurationFiles: [])

        let moduleAFile = SwiftLintFile(
            path: scenarioPath.appending(path: "ModuleA/File.swift").filepath
        )!
        let moduleBFile = SwiftLintFile(
            path: scenarioPath.appending(path: "ModuleB/File.swift").filepath
        )!

        XCTAssertTrue(
            config.configuration(for: moduleAFile).rules
                .map { type(of: $0).identifier }
                .contains("explicit_type_interface")
        )

        XCTAssertFalse(
            config.configuration(for: moduleBFile).rules
                .map { type(of: $0).identifier }
                .contains("explicit_type_interface")
        )
    }

    func testNestedConfigurationDisabledByConfigFlag() {
        let scenarioPath = fixturePath("_4_nested_basic")

        let moduleAFile = SwiftLintFile(
            path: scenarioPath.appending(path: "ModuleB/File.swift").filepath
        )!

        XCTAssertFalse(
            Configuration(configurationFiles: [scenarioPath.appending(path: "root.yml").filepath])
                .configuration(for: moduleAFile)
                .rules
                .map { type(of: $0).identifier }
                .contains("explicit_type_interface")
        )
    }
}
